package org.discala.scopes

import org.discala.Scope
import collection.immutable.Map
import org.discala.util.logging.Logged

/**
 * The singleton scope ensures that only one instance of a particular registered type is
 * ever created. Instances are managed internally within the singleton scope. This is an
 * implementation of the singleton design pattern in a way compatible with dependency
 * injection.
 * <p>
 * When registering a singleton, a provider is passed that generates new instances of the
 * type being registered. The singleton scope returns a new provider that ensures that the
 * registered provider is called only once. Any additional invocations return the same instance
 * that was created on the first invocation.
 * <p>
 * By default, the singleton scope initialises all singletons lazily when the provider is
 * first called. In many cases this is not a sensible strategy because the singleton may
 * be expensive or time consuming to create. The scope therefore also supports eager
 * singletons that are created at registration time.
 *
 * <code>
 * import Singleton.Create._
 * val service = singleton(() => new MyService)        // A lazy singleton
 * val service = singleton(Lazy, () => new MyService)  // A lazy singleton
 * val service = singleton(Eager, () => new MyService) // An eager singleton
 * </code>
 *
 * When using the non-qualifier singleton implementation, singleton instances are registered
 * with their key being the return class name from the supplied provider. This avoids the
 * need for explicit naming strings in the code in most cases. However, there are
 * occasions where multiple singletons of the same type are required in an application.
 * In this case, the alternative qualifier based methods can be used to uniquely identify
 * each instance.
 *
 * <code>
 * val dbService = singleton("database", new MyService)
 * val dbService = singleton("webservice", new MyService)
 * </code>
 *
 * @author Chris Turner
 */
trait Singleton extends Scope with Logged {

  import Create._

  private[this] var instances = Map[String, InstanceHolder]()
  info("Initialising singleton scope")

  /**
   * Returns a provider that manages the singleton lifecycle using the supplied
   * provider parameter to create the singleton instance. Uses lazy initialisation
   * and the provider return type class name as the qualifier.
   *
   * @param provider The provider that creates new instances
   * @return The singleton scope managed provider
   */
  def singleton[T](provider: () => T): () => T = singleton[T](Create.Lazy, provider)

  /**
   * Returns a provider that manages the singleton lifecycle using the supplied
   * provider parameter to create the singleton instance. Uses the initilisation
   * scheme in the passed parameter and the provider return type class name as the qualifier.
   *
   * @param create The initialisation scheme for the singleton instance
   * @param provider The provider that creates new instances
   * @return The singleton scope managed provider
   */
  def singleton[T](create: Create, provider: () => T): () => T = singleton[T](generateQualifier(provider), create, provider)

  /**
   * Returns a provider that manages the singleton lifecycle using the supplied
   * provider parameter to create the singleton instance. Uses lazy initialisation
   * and the supplied qualifier to identify the singleton.
   *
   * @param qualifier The unique name for the singleton instance
   * @param provider The provider that creates new instances
   * @return The singleton scope managed provider
   */
  def singleton[T](qualifier: String, provider: () => T): () => T = singleton[T](qualifier, Create.Lazy, provider)

  /**
   * Returns a provider that manages the singleton lifecycle using the supplied
   * provider parameter to create the singleton instance. Uses the initilisation
   * scheme in the passed parameter and the supplied qualifier to identify the singleton.
   *
   * @param qualifier The unique name for the singleton instance
   * @param create The initialisation scheme for the singleton instance
   * @param provider The provider that creates new instances
   * @return The singleton scope managed provider
   */
  def singleton[T](qualifier:String, create: Create, provider: () => T): () => T = {
    trace("Registering singleton qualified by: {}, with create type: {}", qualifier, create)
    initialiseEager(initialiseForQualifier(qualifier), create, provider)
    () => obtainSingleton(qualifier, provider)
  }

  private class InstanceHolder {
    var instance: Option[Any] = None
  }

  private[this] def obtainSingleton[T](qualifier: String, provider: () => T): T = {
    var instanceHolder = instances(qualifier)
    require(instanceHolder != null)

    instanceHolder.synchronized {
      if ( instanceHolder.instance == None ) {
        debug("Creating singleton instance qualified by: {}", qualifier)
        val newInstance = provider()
        instanceHolder.instance = Some(newInstance)
        trace("Created new singleton instance: {}", newInstance)
        newInstance
      }
      else {
        debug("Obtaining existing singleton instance qualified by: {}", qualifier)
        instanceHolder.instance.get.asInstanceOf[T]
      }
    }
  }

  private[this] def generateQualifier[T](provider: () => T): String = {
    provider.getClass.getMethod("apply").getReturnType.getName
  }

  private[this] def initialiseForQualifier(qualifier: String): String = {
    require(qualifier != null)
    require(qualifier.length > 0)
    if ( instances.contains(qualifier) ) {
      error("Multiple singletons registered with the same qualifier: {}", qualifier)
      throw new IllegalStateException("Multiple singletons registered with the same qualifier: " + qualifier)
    }

    trace("Creating new singleton instance holder for qualifier: {}", qualifier)
    instances = instances.updated(qualifier, new InstanceHolder)
    qualifier
  }

  private[this] def initialiseEager[T](qualifier: String, create: Create, provider: () => T) = {
    require(create != null)
    require(provider != null)
    if ( create == Eager ) {
      trace("Initialialising eager singleton with qualifier: {}", qualifier)
      obtainSingleton(qualifier, provider)
    }
  }
}

/**
 * Object holding the creation types enumeration for controlling when singletons
 * are initialised.
 */
object Create extends Enumeration {
  type Create = Value
  var Lazy, Eager = Value
}